#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.IO;
using BitwiseOre.IO;
using BitwiseOre.Properties;
using BitwiseOre.SharedMemory.Core;
using BitwiseOre.SharedMemory.JetBrains;
using BitwiseOre.SharedMemory.Utils;

namespace BitwiseOre.SharedMemory
{
    /// <summary>
    /// Listens for connections from shared memory clients.
    /// </summary>
    [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
    public sealed class SharedMemoryListener : IDisposable, IListener
    {
        private static readonly string UnableToAcceptMessage = Resources.SharedMemoryListener_UnableToAcceptMessage;
        private static readonly string UnableToWaitForMessage = Resources.SharedMemoryListener_UnableToWaitForMessage;
        private static readonly int DefaultAcceptTimeout = (int) TimeSpan.FromMinutes(10).TotalMilliseconds;
        private static readonly int DefaultWaitForTimeout = (int) TimeSpan.FromMinutes(10).TotalMilliseconds;

        private readonly IDisposer<SharedMemoryListener> _disposer;
        private readonly IAcceptor _acceptor;
        private readonly Func<Guid, SharedMemoryStream> _streamFactory;
        private readonly IAsyncResultEnder<SharedMemoryStream> _streamResultEnder;
        private readonly IAsyncResultEnder _asyncResultEnder;

        /// <summary>
        /// Creates a <see cref="SharedMemoryListener"/> to listen at the given name.
        /// </summary>
        /// <param name="name">
        ///     The name at which to listen for connections.
        /// </param>
        /// <remarks>
        ///     This overload of <c>Create</c> uses the default <see cref="SharedMemoryContext"/>.
        /// </remarks>
        /// <exception cref="NameAlreadyInUseException">
        ///     A <see cref="SharedMemoryListener"/> named <paramref name="name"/> already exisits or
        ///     there is insufficient space in the page file to create this listener.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="name"/> is <c>null</c>.
        /// </exception>
        /// <returns>
        ///     The newly created <see cref="SharedMemoryListener"/>.
        /// </returns>
        public static SharedMemoryListener Create(string name)
        {
            return Create(new SharedMemoryContext(), name);
        }

        /// <summary>
        /// Creates a <see cref="SharedMemoryListener"/> to listen at the given name.
        /// </summary>
        /// <param name="context">
        ///     The context in which to create the listner.
        /// </param>
        /// <param name="name">
        ///     The name at which to listen for connections.
        /// </param>
        /// <exception cref="NameAlreadyInUseException">
        ///     A <see cref="SharedMemoryListener"/> named <paramref name="name"/> already exisits or
        ///     there is insufficient space in the page file to create this listener.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="context"/> or <paramref name="name"/> is <c>null</c>.
        /// </exception>
        /// <returns>
        ///     The newly created <see cref="SharedMemoryListener"/>.
        /// </returns>
        public static SharedMemoryListener Create(SharedMemoryContext context, string name)
        {
            if (context == null) throw new ArgumentNullException("context");
            if (name == null) throw new ArgumentNullException("name");

            try
            {
                return context.CreateListener(name);
            }
            catch (IOException e)
            {
                throw new NameAlreadyInUseException(UnableToCreateMessage(name), e);
            }
            catch(InvalidOperationException e)
            {
                throw new NameAlreadyInUseException(UnableToCreateMessage(name), e);
            }
        }

        internal SharedMemoryListener(IDisposer<SharedMemoryListener> disposer, IAcceptor acceptor,
                                      Func<Guid, SharedMemoryStream> streamFactory,
                                      IAsyncResultEnder<SharedMemoryStream> streamResultEnder,
                                      IAsyncResultEnder asyncResultEnder)
        {
            _disposer = disposer;
            _acceptor = acceptor;
            _streamFactory = streamFactory;
            _streamResultEnder = streamResultEnder;
            _asyncResultEnder = asyncResultEnder;

            AcceptTimeout = DefaultAcceptTimeout;
            WaitForTimeout = DefaultWaitForTimeout;
        }

        /// <summary>
        /// Releases all unmanged resourses associated with this <see cref="SharedMemoryListener"/>.
        /// </summary>
        public void Dispose()
        {
            _disposer.Dispose();
        }

        /// <summary>
        /// Accepts a pending connection request.
        /// </summary>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="SharedMemoryListener"/> has been disposed.
        /// </exception>
        /// <exception cref="TimeoutException">
        ///     The accept operation timed out.
        /// </exception>
        /// <exception cref="SharedMemoryException">
        ///     An unexpected problem occured when attempting to accept a connection.
        /// </exception>
        /// <returns>
        ///     A <see cref="SharedMemoryStream"/> representing the new conntection.
        /// </returns>
        public DisconnectableStream Accept()
        {
            return EndAccept(BeginAccept(null, null));
        }

        /// <summary>
        /// Begins an asynchronous operation to accept a pending connection request.
        /// </summary>
        /// <param name="callback">
        ///     An <see cref="AsyncCallback"/> that references the method to invoke when
        ///     the operation is complete.
        /// </param>
        /// <param name="state">
        ///     A user-defined object containing information about the accept operation.
        /// </param>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="SharedMemoryListener"/> has been disposed.
        /// </exception>
        /// <returns>
        ///     An <see cref="IAsyncResult"/> that reference the asynchronous accept operation.
        /// </returns>
        public IAsyncResult BeginAccept(AsyncCallback callback, object state)
        {
            _disposer.ThrowIfDisposed();

            return _acceptor.Accept(TimeSpan.FromMilliseconds(AcceptTimeout), _streamFactory)
                .WithAsyncCallback(callback, state);
        }

        /// <summary>
        /// Completes an asynchronous operation to accept a pending connection request.
        /// </summary>
        /// <param name="asyncResult">
        ///     The asynchroous operation to complete. Must be the return value of
        ///     <see cref="BeginAccept"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="asyncResult"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="asyncResult"/> is not the return value of <see cref="BeginAccept"/>.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="SharedMemoryListener"/> has been disposed.
        /// </exception>
        /// <exception cref="TimeoutException">
        ///     The accept operation timed out.
        /// </exception>
        /// <exception cref="SharedMemoryException">
        ///     An unexpected problem occured when attempting to accept a connection.
        /// </exception>
        /// <returns>
        ///     A <see cref="SharedMemoryStream"/> representing the new connection.
        /// </returns>
        public DisconnectableStream EndAccept(IAsyncResult asyncResult)
        {
            _disposer.ThrowIfDisposed();

            return _streamResultEnder.EndTask(asyncResult, ae => new SharedMemoryException(UnableToAcceptMessage, ae));
        }

        ///<summary>
        /// Gets or set a value, in milliseconds, that determines how long the listener will
        /// attempt to accept a pending connection request before timing out.
        ///</summary>
        public int AcceptTimeout { get; set; }

        ///<summary>
        /// Blocks waiting for a connection request to this listner.
        ///</summary>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="SharedMemoryListener"/> has been disposed.
        /// </exception>
        /// <exception cref="TimeoutException">
        ///     The wait for operation timed out.
        /// </exception>
        /// <exception cref="SharedMemoryException">
        ///     An unexpected problem occured when attempting to wait for a pending connection.
        /// </exception>
        public void WaitFor()
        {
            EndWaitFor(BeginWaitFor(null, null));
        }

        ///<summary>
        /// Begins an asynchronous operation to wait for a connection request
        /// to this listner.
        ///</summary>
        /// <param name="callback">
        ///     An <see cref="AsyncCallback"/> that references the method to invoke when
        ///     the operation is complete.
        /// </param>
        /// <param name="state">
        ///     A user-defined object containing information about the accept operation.
        /// </param>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="SharedMemoryListener"/> has been disposed.
        /// </exception>
        /// <returns>
        ///     An <see cref="IAsyncResult"/> that reference the asynchronous wait operation.
        /// </returns>
        public IAsyncResult BeginWaitFor(AsyncCallback callback, object state)
        {
            _disposer.ThrowIfDisposed();

            return _acceptor.WaitFor(TimeSpan.FromMilliseconds(WaitForTimeout))
                .WithAsyncCallback(callback, state);
        }

        /// <summary>
        /// Completes an asynchronous operation to wait for a connection request.
        /// </summary>
        /// <param name="asyncResult">
        ///     The asynchronous operation to complete. Must be a return values from
        ///     <see cref="BeginWaitFor"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="asyncResult"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="asyncResult"/> is not the return value of <see cref="BeginWaitFor"/>.
        /// </exception>
        /// <exception cref="ObjectDisposedException">
        ///     The <see cref="SharedMemoryListener"/> has been disposed.
        /// </exception>
        /// <exception cref="TimeoutException">
        ///     The wait for operation timed out.
        /// </exception>
        /// <exception cref="SharedMemoryException">
        ///     An unexpected problem occured when attempting to wait for a pending connection.
        /// </exception>
        public void EndWaitFor(IAsyncResult asyncResult)
        {
            _disposer.ThrowIfDisposed();

            _asyncResultEnder.EndTask(asyncResult, ae => new SharedMemoryException(UnableToWaitForMessage, ae));
        }

        /// <summary>
        /// Gets or set a value, in miliseconds, that determines how long the listener
        /// will wait for a connection request before timing out.
        /// </summary>
        public int WaitForTimeout { get; set; }

        private static string UnableToCreateMessage(string name)
        {
            var format = Resources.SharedMemoryListener_UnableToCreateMessage;
            return string.Format(format, name);
        }
    }
}